/*
 *
 * Copyright 2020 gRPC authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

// Binary main implements a client for Greeter service using gRPC's client-side
// support for xDS APIs.
package main

import (
	"context"
	"flag"
	"fmt"
	"github.com/spf13/pflag"
	istiolog "istio.io/pkg/log"
	"log"
	"net"
	"os"
	"os/signal"
	"syscall"
	"time"
	"tmlake.com/api-gateway/istio-agent/cmd/options"
	"tmlake.com/api-gateway/istio-agent/pkg/grpclog"
	internalgrpclog "tmlake.com/api-gateway/istio-agent/pkg/grpclog/inside"
	"tmlake.com/api-gateway/istio-agent/pkg/model"
	"tmlake.com/api-gateway/istio-agent/pkg/util/network"
	"tmlake.com/api-gateway/istio-agent/proxy/file"
	"tmlake.com/api-gateway/istio-agent/proxy/njet"
	"tmlake.com/api-gateway/istio-agent/proxy/njetcontroller/controller"
	njt_config "tmlake.com/api-gateway/istio-agent/proxy/njetcontroller/controller/config"
	"tmlake.com/api-gateway/istio-agent/version"
	_ "tmlake.com/api-gateway/istio-agent/xds"
	"tmlake.com/api-gateway/pkg/config/constants"
	istio_agent "tmlake.com/api-gateway/pkg/istio-agent"
)

const (
	localHostIPv4 = "127.0.0.1"
	localHostIPv6 = "[::1]"
)

var (
	tokenManagerPlugin string
)

var (
	logger         = grpclog.Component("main")
	mainLogger     = internalgrpclog.NewPrefixLogger(logger, "")
	loggingOptions = istiolog.DefaultOptions()
)

type exiter func(code int)

func handleSigterm(agent *istio_agent.Agent, exit exiter) {
	signalChan := make(chan os.Signal, 1)
	signal.Notify(signalChan, syscall.SIGTERM, syscall.SIGINT)
	<-signalChan
	mainLogger.Infof("Received SIGTERM or SIGINT, shutting down")
	fmt.Printf("Received SIGTERM or SIGINT, shutting down\n")

	exitCode := 0
	err := agent.Close()
	if err != nil {
		mainLogger.Infof("Error during shutdown: %v", err)
		exitCode = 1
	}

	mainLogger.Infof("Handled quit, delaying controller exit for %d seconds", agent.NjetOpts.PostShutdownGracePeriod)
	time.Sleep(time.Duration(agent.NjetOpts.PostShutdownGracePeriod) * time.Second)

	mainLogger.Info("Exiting", "code", exitCode)
	log.Println("Exiting", "code", exitCode)
	exit(exitCode)
}

func parseFlags() (bool, *controller.Configuration, error) {
	var (
		flags = pflag.NewFlagSet("", pflag.ExitOnError)

		defHealthzURL = flags.String("health-check-path", "/healthz",
			`URL path of the health check endpoint.
Configured inside the NGINX status server. All requests received on the port
defined by the healthz-port parameter are forwarded internally to this path.`)

		defHealthCheckTimeout = flags.Int("health-check-timeout", 10, `Time limit, in seconds, for a probe to health-check-path to succeed.`)

		showVersion = flags.Bool("version", false,
			`Show release information about the NGINX Ingress controller and exit.`)

		enableSSLChainCompletion = flags.Bool("enable-ssl-chain-completion", false,
			`Autocomplete SSL certificate chains with missing intermediate CA certificates.
Certificates uploaded to Kubernetes must have the "Authority Information Access" X.509 v3
extension for this to succeed.`)

		outboundPort = flags.Int("outbound-traffic-port", 15001, `Port to use for servicing Outbound traffic(redirected by iptables).`)
		inboundPort  = flags.Int("inbound-traffic-port", 15006, `Port to use for servicing Inbound traffic(redirected by iptables).`)
		httpPort     = flags.Int("http-port", 8088, `Port to use for servicing HTTP traffic.`)
		httpsPort    = flags.Int("https-port", 443, `Port to use for servicing HTTPS traffic.`)

		sslProxyPort  = flags.Int("ssl-passthrough-proxy-port", 442, `Port to use internally for SSL Passthrough.`)
		defServerPort = flags.Int("default-server-port", 8181, `Port to use for exposing the default server (catch-all).`)
		healthzPort   = flags.Int("healthz-port", 10254, "Port to use for the healthz endpoint.")

		statusPort = flags.Int("status-port", 10246, `Port to use for the lua HTTP endpoint configuration.`)
		streamPort = flags.Int("stream-port", 10247, "Port to use for the lua TCP/UDP endpoint configuration.")

		profilerPort = flags.Int("profiler-port", 10245, "Port to use for expose the ingress controller Go profiler when it is enabled.")

		shutdownGracePeriod     = flags.Int("shutdown-grace-period", 0, "Seconds to wait after receiving the shutdown signal, before stopping the nginx process.")
		postShutdownGracePeriod = flags.Int("post-shutdown-grace-period", 10, "Seconds to wait after the nginx process has stopped before controller exits.")
	)

	flag.Set("logtostderr", "true")

	flags.AddGoFlagSet(flag.CommandLine)
	flags.Parse(os.Args)

	// Workaround for this issue:
	// https://github.com/kubernetes/kubernetes/issues/17162
	flag.CommandLine.Parse([]string{})

	if *showVersion {
		return true, nil, nil
	}

	njet.StatusPort = *statusPort
	njet.StreamPort = *streamPort
	njet.ProfilerPort = *profilerPort

	njet.HealthPath = *defHealthzURL

	if *defHealthCheckTimeout > 0 {
		njet.HealthCheckTimeout = time.Duration(*defHealthCheckTimeout) * time.Second
	}

	njt_config.EnableSSLChainCompletion = *enableSSLChainCompletion

	config := &controller.Configuration{

		ListenPorts: &njt_config.ListenPorts{
			Default:   *defServerPort,
			Health:    *healthzPort,
			Outbound:  *outboundPort,
			Inbound:   *inboundPort,
			HTTPProxy: *httpPort,
			HTTPS:     *httpsPort,
			SSLProxy:  *sslProxyPort,
		},
		ShutdownGracePeriod:     *shutdownGracePeriod,
		PostShutdownGracePeriod: *postShutdownGracePeriod,
	}

	return false, config, nil
}

func initProxy(args []string) (*model.Proxy, error) {
	proxy := &model.Proxy{
		Type: model.SidecarProxy,
	}
	if len(args) > 0 {
		proxy.Type = model.NodeType(args[0])
		if !model.IsApplicationNodeType(proxy.Type) {
			return nil, fmt.Errorf("Invalid proxy Type: " + string(proxy.Type))
		}
	}

	//podIP:The virtualInbound configuration depends on podIP
	podIP := net.ParseIP(options.InstanceIPVar.Get()) // protobuf encoding of IP_ADDRESS type
	if podIP != nil {
		proxy.IPAddresses = []string{podIP.String()}
	}

	// Obtain all the IPs from the node
	if ipAddrs, ok := network.GetPrivateIPs(context.Background()); ok {
		if len(proxy.IPAddresses) == 1 {
			for _, ip := range ipAddrs {
				// prevent duplicate ips, the first one must be the pod ip
				// as we pick the first ip as pod ip in istiod
				if proxy.IPAddresses[0] != ip {
					proxy.IPAddresses = append(proxy.IPAddresses, ip)
				}
			}
		} else {
			proxy.IPAddresses = append(proxy.IPAddresses, ipAddrs...)
		}
	}

	// No IP addresses provided, append 127.0.0.1 for ipv4 and ::1 for ipv6
	if len(proxy.IPAddresses) == 0 {
		proxy.IPAddresses = append(proxy.IPAddresses, localHostIPv4, localHostIPv6)
	}

	// Extract pod variables.
	podName := options.PodNameVar.Get()
	podNamespace := options.PodNamespaceVar.Get()
	if podNamespace == "" {
		podNamespace = "default"
	}
	proxy.ID = podName + "." + podNamespace

	// If not set, set a default based on platform - podNamespace.svc.cluster.local for
	// K8S
	proxy.DNSDomain = getDNSDomain(podNamespace, "")
	istiolog.WithLabels("ips", proxy.IPAddresses, "type", proxy.Type, "id", proxy.ID, "domain", proxy.DNSDomain).Info("Proxy role")

	return proxy, nil
}

func getDNSDomain(podNamespace, domain string) string {
	if len(domain) == 0 {
		domain = podNamespace + ".svc." + constants.DefaultKubernetesDomain
	}
	return domain
}

func main() {
	loggingOptions.SetOutputLevel(istiolog.OverrideScopeName, istiolog.DebugLevel)

	if err := istiolog.Configure(loggingOptions); err != nil {
		mainLogger.Fatal(err)
	}
	mainLogger.Infof("start...")
	err := file.CreateRequiredDirectories()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(version.String())

	showVersion, conf, err := parseFlags()
	if showVersion {
		os.Exit(0)
	}

	if err != nil {
		mainLogger.Fatal(err)
	}

	proxy, err := initProxy(nil)
	if err != nil {
		log.Fatal(err)
	}

	secOpts, err := options.NewSecurityOptions(nil, tokenManagerPlugin)
	if err != nil {
		mainLogger.Fatal(err)
	}

	agentOptions := options.NewAgentOptions(proxy)
	agent := istio_agent.NewAgent(nil, agentOptions, secOpts, conf)

	// Start in process security, xds, and njet.
	err = agent.Run()
	if err != nil {
		mainLogger.Fatal(err)
	}

	handleSigterm(agent, func(code int) {
		os.Exit(code)
	})
}
